// test_backscatter.cpp

#include <iostream>
#include <fstream>
#include <string>
#include <algorithm>
#include <numeric>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <list>

#include <quigon.h>
#include "stk_para.h"
#include "stk_lidar.h"
#include "stk_lidar_io.h"
#include "basic_process.h"
#include "noise_filter.h"
#include "fernald.h"
#include "output_lidarpro.h"

using namespace std;
using namespace quigon;
using namespace boost::posix_time;

int main(int argc, char *argv[])
{
	string start_t, end_t;
	double distance, aver_seconds;
	if (argc < 5)
	{
		cout << "usage: test_backscatter yyyymmddThhmmss yyyymmddThhmmss distance aver_seconds" << endl;
		cout << "The capital 'T' in the 'iso time string' is optional" << endl;
		exit(0);
	}
	else
	{
		start_t = argv[1];
		end_t = argv[2];
		distance = atof(argv[3]);
		aver_seconds = atof(argv[4]);
	}

	// Check the date_time format, if not iso string, add 'T' into it.
	if (start_t[8] != 'T')
		start_t.insert(8, 1, 'T');
	if (end_t[8] != 'T')
		end_t.insert(8, 1, 'T');

	// Load lidar parameters
	cout << "Loading parameters" << endl;
	STK_Para para("../settings/stk_lidar_para.txt");

	// find out how many bins do we need in the lines.
	int max_line_size = para.distance_i[distance] + 1;

	// Make a container.
	cout << "Making a container for the data." << endl;
	list<STK_Line> container;

	// import the data.
	cout << "Importing data..." << endl;
	import(
	        container,
	        from_iso_string(start_t),		// boost::posix_time::from_iso_string
	        from_iso_string(end_t),		// take care of the "T" in the string
	        para
	);

	cout << "Lines num: " << container.size() << endl;

	// Make an average machine.
	cout << "Making an average machine" << endl;
	Average_Machine<STK_Line> aver(aver_seconds, stk_aver_attr, false);
	aver(container);

	// Make a functor for data correction.
	Correction<STK_Line, STK_Para> corr(
	        para,
	        (Status::BACKGROUND |
	         Status::AFTERPULSE |
	         Status::DISTANCE   |
	         Status::OVERLAP    |
	         Status::ENERGY
	        ) ,
	        false
	);

	// Make a functor to fill the lowest blind range of lidar
	Fill_Lowest filler(para, 5);

	Serie<string, double> tune_para("../settings/tune/stk_bs_diff2.para.txt");
	
	// Make a functor for filtering
	Noise_Filter_Proto filter(
	        para.zero_meter_index,
	        para.distance_i[distance],
	        tune_para["filter_buddy_low"],
	        tune_para["filter_buddy_high"],
	        para.zero_meter_index,
	        para.distance_i[distance]);
//    double trigger_value = filter_para["trigger_value"];
//    size_t lowest_trigger_index = filter_para["lowest_trigger_index"];
	for (list<STK_Line>::iterator i = container.begin();
	     i != container.end();
	     ++i)
	{
		i->resize(max_line_size);
		corr(*i);
		filler(*i);
		filter(*i);
	}
	
	Time_Serie cbh;
	Time_Serie pbl;
	list<STK_Line> diff_container(container);
	double near_zero_thres = tune_para["near_zero_thres"];
	size_t cloud_detect_start_index = tune_para["cloud_detect_start_index"];
	double cloud_detect_thres1 = tune_para["cloud_detect_thres1"];
	double cloud_detect_thres2 = tune_para["cloud_detect_thres2"];
	
	for (list<STK_Line>::iterator i = container.begin(), j = diff_container.begin();
	     i != container.end() && j != diff_container.end();
	     ++i, ++j)
	{
		adjacent_difference(i->begin(), i->end(), j->begin());

		bool met_cloud = false;
		STK_Line acc_signal = gen_acc_profile(*i, para.zero_meter_index, 1.0);
		
		for(int ii = para.zero_meter_index; ii < i->size(); ++ii)
		{
			if(met_cloud)
			{
				(*j)[ii] = 0;
				continue;
			}

			(*j)[ii] *= acc_signal[ii];

			if( (*i)[ii] > near_zero_thres )
                        {
                                (*j)[ii] /= (*i)[ii];
                        }
                        else
                        {
                                (*j)[ii] /= near_zero_thres;
                        }

			if( ii >= cloud_detect_start_index && 
				( 
					(*j)[ii] > cloud_detect_thres1 || 
					((*j)[ii] > cloud_detect_thres2 && (*j)[ii - 1] > cloud_detect_thres2 )
				)
			  )
			{
				met_cloud = true;
				cbh.add_entry(j->t(), para.height(ii, j->t()));
			}
		}
	}

	// fill time gaps.
	cout << "Filling time gap..." << endl;
	fill_time_gap(diff_container, aver_seconds);

	// On output
	string output_file_name_base = string("../output/stk_") + start_t + "_" + end_t;
        string::iterator str_iter =
                remove(output_file_name_base.begin(), output_file_name_base.end(), 'T');
        output_file_name_base.erase(str_iter, output_file_name_base.end());

        string diff2_file_name = output_file_name_base + ".bs_diff2.txt";
	string bs_file_name = output_file_name_base + ".backscat.txt";
        string cbh_file_name = output_file_name_base + ".bs_cbh1.txt";
	string pbl_file_name = output_file_name_base + ".bs_pbl.txt";

	// Make a functor for data printing.
	Output_Lidarpro printer_diff2(diff2_file_name, para.zero_meter_index);
	Output_Lidarpro printer_bs(bs_file_name, para.zero_meter_index);

	cout << "Outputing data to: " << output_file_name_base << " .diff2/.backscat/.bs_cbh1" << endl;
	for (list<STK_Line>::iterator j = diff_container.begin();
	     j != diff_container.end();
	     ++j)
	{
		printer_diff2(*j);
		printer_bs(*j);
	}
	cbh.save(cbh_file_name);

	cout << "Task completed" << endl;
	return 0;
}

